[integrations] Enhanced MCP server (alpha tool suite)#202
Open
alanshurafa wants to merge 11 commits into
Open
Conversation
7 tasks
Collaborator
Author
|
Conflicts with |
…tchWithTimeout Adds an AbortController-backed fetchWithTimeout helper to _shared/helpers.ts and rewires all 5 outbound fetches (OpenRouter + OpenAI embeddings; OpenRouter + OpenAI + Anthropic chat completions) through it. Default 60s, override via FETCH_TIMEOUT_MS env. Also widens isTransientError to match the new "fetch timeout" error string plus "aborted" and 504, and adds a sibling isFatalProviderError for 400/401/402/403 so BLOCKER-2 can fail-fast on hard auth/quota errors instead of cascading to fallback providers. Why: on upstream provider stall every caller was hanging until the Supabase Edge Function runtime killed the connection (~150s). With 5 LLM calls per capture in the worst case, a single capture_thought could pin an Edge Function for ~10+ minutes. This is the Wave-wide timeout pattern applied consistently here.
Three layered safeguards so capture_thought cannot rack up unbounded per-request LLM charges: 1. Fingerprint-first dedup in capture_thought — before we pay for any classification or embedding, hash the raw content and check if it already exists. Identical re-captures short-circuit with an action="deduplicated" result. upsert_thought dedups too, but by then we've already burned the enrichment cycle. 2. Global call budget ENHANCED_MCP_MAX_CALLS (default 10000). Edge Function instance tracks cumulative classifier invocations and returns fallback metadata once the cap is hit. Set to 0 to disable classification entirely for bulk imports. 3. Fail-fast on 400/401/402/403 via a new isFatalProviderError. The old path treated ALL errors as cascade-worthy, so a single 402 (payment required) on OpenRouter would fire OpenAI *and* Anthropic in sequence — double-billing the user on the two providers that had nothing to do with the original failure. New path: fatal errors skip the fallback chain entirely. Also caps attempt 3 at exactly ONE fallback provider instead of iterating through all remaining providers. Why: the original extractMetadata could fire up to 4 LLM calls per capture (primary + retry + 2 fallback providers). A batch of 100 captures on a rate-limited primary would easily hit 300+ calls with 1.5s retry delays adding 150+ seconds of wall-clock, and any 402 on OpenRouter would double-bill into OpenAI and Anthropic regardless of whether either could plausibly fix the problem.
…ding sensitivity update_thought was writing detectSensitivity(content).tier directly to the row, which meant editing a `personal` thought to remove the sensitive phrasing silently relabeled it as `standard` — and any restricted pattern in the new content was happily persisted to the cloud even though capture_thought refuses that same content. Fix: 1. Pre-flight reject if the NEW content trips any RESTRICTED_PATTERN. Matches capture_thought's behavior and returns the detection reason in the error so the caller knows why. 2. Use resolveSensitivityTier() with the EXISTING row's tier as the floor. Escalation-only semantics: personal -> standard is blocked, standard -> personal / personal -> restricted still work. This is the same helper prepareThoughtPayload already uses everywhere else. Why: this was a real data-leak vector. A user captures "my salary is $120k" as `personal`, later rephrases it to "my income situation is comfortable", and the row goes back to `standard`. The next broad list_thoughts exposes it to any connected client. Update paths must maintain the escalation invariant that capture paths enforce.
…release Removes the delete_thought tool registration, the README table row, and the 14 -> 13 tool count everywhere (README intro, Expected Outcome, Tool Surface Area, metadata.json description). Renumbers the remaining section comments in index.ts. Adds an "Intentionally Excluded From This Release" section to the README explaining why delete_thought will ship in a follow-up: hard DELETE has no tombstone path on the enhanced-thoughts schema today, and the maintainer's PR NateBJones-Projects#127 guidance was "depreciate and version rather than delete." Shipping a safe soft-delete requires a `deleted_at` column and a restore_thought sibling that don't exist yet. Why: the drafted implementation was hard DELETE on a row with no deleted_at column, no audit trail, no restore path. Aligning with PR NateBJones-Projects#127 posture is cheaper than trying to bolt on soft-delete here without schema support — we'll land both tool and schema changes together in a later PR.
…prefix Renames the four tools that share names with server/index.ts so both MCP servers can stay connected without the model seeing duplicate tool entries: - search_thoughts -> brain_search_thoughts - list_thoughts -> brain_list_thoughts - capture_thought -> brain_capture_thought - thought_stats -> brain_thought_stats Also updates the README What-It-Does, Step 4, Expected Outcome, and Tool Reference sections to reflect the new names and explain the collision-prevention intent, plus matching section comments and internal error-log labels in index.ts for grep-ability. Why: Claude Desktop and most MCP clients list connector tools in a flat namespace. When the stock server and this server both expose `capture_thought`, the model has to guess which one the user meant; if it picks the stock one, there's no sensitivity pre-flight and "restricted content stays local" silently breaks. `brain_` prefix is a cheap one-pass rename that eliminates the footgun by design.
…e tableExists
Two related fixes:
1. The schema guard in ops_source_monitor was looking for
`ops_source_volume_24h`, a view name that exists in neither this
repo nor the brain-health-monitoring recipe. Result: once the user
installed the recipe (which defines `ops_source_ingestion_24h`,
`ops_source_errors_24h`, `ops_source_recent_failures`), the tool
STILL returned "install required views" because the guard looked
for a view nothing creates. Fix: check `ops_source_ingestion_24h`
(one of the real views) and add a partial-install detection that
returns a graceful "only partially installed" response if any one
of the three views is missing.
2. `tableExists` previously required the target to have an `id`
column (`select("id")`). That works for tables but not for views
like `ops_source_errors_24h` which has only
`(source, error_events_24h)`. Switched to
`select("*", { head: true, count: "exact" }).limit(0)` which
performs a HEAD request with no data transfer and no column-name
dependency, so it works on any table or view.
Why: without this fix the tool never activates even when the user
installs exactly the recipe the README told them to install — a
pure dead-end UX. And `tableExists` was one unusual view schema away
from false-negatives on other operational tooling.
Adds an escapeLikePattern helper that escapes `\`, `%`, and `_` in a
user query before interpolating into an ILIKE pattern. graph_search
now runs `%${escapeLikePattern(query)}%` instead of `%${query}%`.
Why: a user searching for "100%" (e.g. "100% uptime") was producing
the ILIKE pattern `%100%%` which matches every entity whose
canonical_name contains "100" — effectively the whole graph for a
dense brain, capped only by LIMIT. And "a_b" was matching "aab",
"axb", etc. Not SQL injection (PostgREST parameterizes the value)
but a DoS-adjacent correctness bug that passes unit tests on
alphanumeric queries and falls over on real queries.
… fallback Two auth hardening changes: 1. Replace `provided !== MCP_ACCESS_KEY` with a timingSafeEqualStrings helper that prefers crypto.subtle.timingSafeEqual and falls back to a manual XOR loop. Length mismatch short-circuits — acceptable for the fixed 32-char access key; a variable-length key would need a different pattern. 2. Drop the `?key=<access-key>` query-parameter fallback. Auth now requires `x-brain-key: <key>` OR `Authorization: Bearer <key>` only. Query strings end up in Supabase request logs, CDN logs, and any intermediate proxy logs — leaking the credential into places that don't get rotated with the secret itself. Also updated README Step 3 and Troubleshooting to document the header-only posture. Why: timing-safe comparison is the Wave-wide review bar for any bearer-equivalent token, and URL query credentials are a classic "works today, leaks tomorrow" anti-pattern.
… search Two-layer defense for the "top-N then post-filter" correctness bug: 1. Forward start_date / end_date into the match_thoughts RPC filter payload along with exclude_restricted. RPC versions that honour these filter keys will pre-filter at the SQL level before applying the similarity cutoff — making the behavior server-side correct. Older RPCs ignore unknown filter keys and we fall through to (2). 2. When a date filter is active, over-fetch 3x the requested limit (capped at 500) instead of limit + 50. The previous +50 slack was catastrophic on dense recent brains: a top-200 result set where all 200 matches were recent would silently return 0 rows for an old-date query even when relevant matches existed below rank 200. Also documents the limitation in a new README "Known Limitations" section so users running on the older RPC signature understand the workaround (switch to mode: "text" or narrow the query). Why: silent empty results are the worst class of search UX failure because users assume "no matches" rather than "cutoff too tight." The over-fetch cost is bounded at 500 rows, a few milliseconds on any reasonable brain size.
…ture Adds a new "Security" section to the README covering: 1. This server's own auth model (constant-time compare, header-only MCP_ACCESS_KEY, service_role under the hood as the sensitivity- filter boundary). 2. Companion schema risk: the enhanced-thoughts schema installs its three SECURITY DEFINER RPCs with service_role-only grants by default; granting anon/authenticated on those RPCs would be an RLS bypass because SECURITY DEFINER runs with the function owner's privileges. Combined with a publicly-reachable enhanced-mcp deployment that pattern would let anyone with the Supabase project URL + anon key read thought content directly, routing around this server's sensitivity filtering. Why: the README previously advertised the RPC names (which makes them discoverable) without warning that exposing them to anon collapses the whole sensitivity story. A one-paragraph callout costs nothing and is exactly the kind of "safe defaults" note the upstream gate reviewers expect from integration docs.
fcf9258 to
129646e
Compare
Collaborator
Author
|
Rebased onto |
|
Is there a possibility that this is going to be merged soon? I'm trying to work with the entity-extraction schema and entity-extraction worker, and the |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Deno/Supabase Edge Function implementing an alternative MCP server with 13 extended tools. This is NOT a modification to `server/index.ts` — it's a standalone integration that runs alongside the stock core MCP.
Tools (4 renamed with `brain_` prefix to avoid collision with stock tool names):
Hardened per Wave 2.5 review:
Intentionally excluded: `delete_thought`
Per the conversation on #127, destructive actions should be "deprecate + version" rather than hard DELETE. This PR ships without `delete_thought`. Will follow up once `schemas/enhanced-thoughts` gains a `deleted_at` column and a recycle-bin restoration tool. That's intentionally a separate, smaller PR for maintainer review.
Why
Stock MCP is deliberately minimal. This integration adds tools that benefit from the enhanced-thoughts schema (sensitivity gating, quality-score ranking) and the entity-extraction schema (graph traversal). Users can deploy both servers side-by-side — the `brain_` prefix keeps tool names unambiguous in Claude Desktop's connector UI.
Replaces the content from the closed PR bundle, restructured to avoid any `server/` edits (the #127 closure reason).
Test plan